package br.ufal.ic.fracao.especialist.inference;

import br.ufal.ic.fracao.especialist.domain.Item;
import br.ufal.ic.fracao.especialist.domain.Rule;
import br.ufal.ic.fracao.especialist.infra.Persistence;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane;
import org.jdom.JDOMException;

public class InferenceAlgorithm {
    
    private Persistence objectPersistence;
    private List<Item> memoryWorking;
    
    public InferenceAlgorithm() {
        objectPersistence = new Persistence();
        memoryWorking = new ArrayList<Item>();
    }
    
    public boolean addToMemoryWorking(Item _item) {
        for (Item item : memoryWorking) {
            if (_item.getDescription().equals(item.getDescription())) {
                return false;
            }
        }
        memoryWorking.add(_item);
        return true;
    }
    
    public void cleanMemoryWorking() {
        memoryWorking.clear();
    }
  
    public String askUserValue(Item _item) {
        String answer = "false";
        answer = JOptionPane.showInputDialog(_item.getDescription() + "? (true, false or why)");
        return answer;
    }
    
    public Double askUserCertaintyFactor() {
        Double d = new Double("0.0");
        
        do {
            d = Double.parseDouble(JOptionPane.showInputDialog("Digite o Fator de certeza"));
        } while (d < 0 || d > 1);
        return d;
    }
    
    public List<String> getConclusions() throws IOException, JDOMException {
        List<String> conclusions = new ArrayList<String>();
        
        for (Rule rule: objectPersistence.getRules()) {
            if (!isPremisse(rule.getConclusion())) {
                conclusions.add(rule.getConclusion().getDescription());
            }
        }
        return conclusions;
    }
    
    public String doForwardChaining(Item problem, boolean doHowExplanation) throws IOException, JDOMException { // MODIFICADO
    	
        String result = "";
        
        for (Rule rule : objectPersistence.getRules()) {
            Node root = new Node();
            
            if (problem.getDescription().equals(rule.getConclusion().getDescription()) &&
                    !isPremisse(rule.getConclusion())) {
                root = buildTree(rule.getConclusion());
                if (forwardChaining(root)) {
                    result = root.getItem().getDescription();
                    if (doHowExplanation) {
                        JOptionPane.showMessageDialog(null, processHowQuestion(root), "How Question", JOptionPane.INFORMATION_MESSAGE);
                    }
                } else {
                    result = "It was not possible to prove: " + root.getItem().getDescription();
                }
            }
        }
        return result;
    }
    
    private boolean forwardChaining(Node _node) throws IOException, JDOMException {
        Item item = new Item();
        boolean isConclusion = false;
        boolean isRuleValid = true;
        Double fc = new Double("1.0");
        
        for (int i = 0; i < _node.getNumberOfChildrens(); i++) {
            if (isRuleValid) {
                item = isInMemoryWorking(_node.getChildren(i).getItem());
                isConclusion = isConclusion(_node.getChildren(i).getItem());
                if (isConclusion) {
                    if (item.getDescription().equals("not in memory")) {
                        isRuleValid = forwardChaining(_node.getChildren(i));
                        if (!isRuleValid) {
                            if (_node.getChildren(i).getItem().getValue().equals("true")) {
                                _node.getChildren(i).getItem().setValue("false");
                            } else {
                                _node.getChildren(i).getItem().setValue("true");
                            }
                        }
                        fc = fc * _node.getChildren(i).getItem().getCertaintyFactor(); // MODIFICADO
                        _node.getChildren(i).getItem().setCertaintyFactor(fc); // MODIFICADO
                        addToMemoryWorking(_node.getChildren(i).getItem());
                        continue;
                    } else {
                        if(item.getValue().equals(_node.getChildren(i).getItem().getValue())) {
                            fc = fc * item.getCertaintyFactor(); // MODIFICADO
                            continue;
                        }
                        return false;
                    }
                }
                if (isConclusionIgnoreValue(_node.getChildren(i).getItem())) {
                    isRuleValid = forwardChaining(_node.getChildren(i));
                    fc = fc * _node.getChildren(i).getItem().getCertaintyFactor(); // MODIFICADO
                    _node.getChildren(i).getItem().setCertaintyFactor(fc); // MODIFICADO
                    if (!isRuleValid) {
                        if (_node.getChildren(i).getItem().getValue().equals("true")) {
                            _node.getChildren(i).getItem().setValue("false");
                        } else {
                            _node.getChildren(i).getItem().setValue("true");
                        }
                    }
                    addToMemoryWorking(_node.getChildren(i).getItem());
                    continue;
                }
                if (!item.getDescription().equals("not in memory") &&
                        item.getValue().equals(_node.getChildren(i).getItem().getValue())) {
                    fc = fc * item.getCertaintyFactor(); // MODIFICADO
                    continue;
                }
                return false;
            }
        }
        _node.getItem().setCertaintyFactor(fc);
        return isRuleValid;
    }
    
    //--------------------------------------BACKWARD CHAINING--------------------------------------
    
    /**
     * Method responsible for doing the backward chaining algorithm.
     */
    public void doBackwardChaining(boolean doHowExplanation) throws IOException, JDOMException {
    	
        for (Rule rule : objectPersistence.getRules()) {
            Node root = new Node();
            
            // if the rule has a conclusion that is not a premisse in another rule, so...
            if (!isPremisse(rule.getConclusion())) {
                // build the tree where this conclusion is the tree's root and the premisses are their
                // childrens.
                root = buildTree(rule.getConclusion());
                // if this rule is valid, return the conclusion.
                if (backwardChaining(root)) {
                    JOptionPane.showMessageDialog(null, root.getItem().getDescription() + " = " +
                            root.getItem().getValue() + "\nFC = " + root.getItem().getCertaintyFactor(),
                            "Backward Chaining", JOptionPane.INFORMATION_MESSAGE);
                    if (doHowExplanation) {
                        JOptionPane.showMessageDialog(null, processHowQuestion(root), "How Question", JOptionPane.INFORMATION_MESSAGE);
                    }
                }
            }
        }
        cleanMemoryWorking();
    }
    
    /**
     * Method that verify if a rule is a valid one.
     */
    private boolean backwardChaining(Node _node) throws IOException, JDOMException {
        Item item = new Item();
        boolean isConclusion = false;
        boolean isRuleValid = true; // identify if the rule is valid.
        Double fc = new Double("1.0"); // MODIFICADO
        
        for (int i = 0; i < _node.getNumberOfChildrens(); i++) {
            if (isRuleValid) {
                item = isInMemoryWorking(_node.getChildren(i).getItem());
                isConclusion = isConclusion(_node.getChildren(i).getItem());
                // if the item is not in memory, then...
                if (item.getDescription().equals("not in memory")) {
                    // if the item is not a rule's conclusion, ask user.
                    if (!isConclusion) {
                        String answer = "why";
                        do {
                            answer = askUserValue(_node.getChildren(i).getItem());
                            if (answer.equalsIgnoreCase("WHY")) {
                                processWhyQuestion(_node);
                            }
                        } while (answer.equalsIgnoreCase("WHY"));
                        item.setValue(answer);
                        item.setDescription(_node.getChildren(i).getItem().getDescription());
                        item.setCertaintyFactor(askUserCertaintyFactor()); // MODIFICADO
                        addToMemoryWorking(item);
                    } else {
                        // if the item is a rule's conclusion go deep in the tree.
                        isRuleValid = backwardChaining(_node.getChildren(i));
                        fc = fc * _node.getChildren(i).getItem().getCertaintyFactor(); // MODIFICADO
                        _node.getChildren(i).getItem().setCertaintyFactor(fc); // MODIFICADO
                        // if all premisses of that rule is true, so add this conclusion to working memory.
                        if (!isRuleValid) {
                            if (_node.getChildren(i).getItem().getValue().equals("true")) {
                                _node.getChildren(i).getItem().setValue("false");
                            } else {
                                _node.getChildren(i).getItem().setValue("true");
                            }
                        }
                        addToMemoryWorking(_node.getChildren(i).getItem());
                        continue;
                    }
                }
                // if the item is in memory, verify if it has the same rule item's value.
                if (item.getValue().equals(_node.getChildren(i).getItem().getValue()) &&
                        isRuleValid) {
                    fc = fc * item.getCertaintyFactor(); // MODIFICADO
                    // if true, go to next node in the tree.
                    continue;
                } else {
                    // if false, invalidate the rule and go to the next tree.
                    return false;
                }
            }
            
        }
        _node.getItem().setCertaintyFactor(fc); // MODIFICADO
        return isRuleValid;
        
    }
    
    //------------------------------------------------------------------------------------------------
    
    /**
     * Method that gives an explanation to the user.
     */
    public void processWhyQuestion(Node _node) {
        String explanation = "IF \n";
        
        for (int i = 0; i < _node.getNumberOfChildrens(); i++) {
            explanation = explanation + _node.getChildren(i).getItem().getDescription() + " = " +
                    _node.getChildren(i).getItem().getValue() + "\n";
        }
        explanation = explanation + "THEN \n" + _node.getItem().getDescription() + " = " +
                _node.getItem().getValue() + "\n";
        JOptionPane.showMessageDialog(null, explanation, "Why Question", JOptionPane.INFORMATION_MESSAGE);
    }
    
    public String processHowQuestion(Node root) throws IOException, JDOMException {
        String explanation = "IF \n";
        
        for (int i = 0; i < root.getNumberOfChildrens(); i++) {
            if (isPremisse(root.getChildren(i).getItem()) && isConclusion(root.getChildren(i).getItem())) {
                explanation = "";
                explanation = explanation + processHowQuestion(root.getChildren(i));
                explanation = explanation + "IF \n";
            }
            explanation = explanation + root.getChildren(i).getItem().getDescription() + " = " +
                    root.getChildren(i).getItem().getValue() + "\n";
        }
        explanation = explanation + "THEN \n" + root.getItem().getDescription() + " = " +
                root.getItem().getValue() + "\n\n";
        return explanation;
    }
    
    /**
     * Method that builds the that is used during the inference.
     */
    private Node buildTree(Item _item) throws IOException, JDOMException {
        Node node = new Node();
        node.addItem(_item);
        
        for (Rule rule : objectPersistence.getRules()) {
            if (_item.getDescription().equals(rule.getConclusion().getDescription()) &&
                    _item.getValue().equals(rule.getConclusion().getValue())) {
                for (Item premisse : rule.getPremisses()) {
                    node.addChildren(buildTree(premisse));
                }
            }
        }
        return node;
    }
    
    /**
     * Method that verify if an item is in memory working.
     */
    private Item isInMemoryWorking(Item _item) {
        Item notInMemory = new Item();
        
        for (Item iteratorItem : memoryWorking) {
            if (_item.getDescription().equals(iteratorItem.getDescription())) {
                return iteratorItem;
            }
        }
        notInMemory.setDescription("not in memory");
        return notInMemory;
    }
    
    /**
     * Method that verify if an item is a rule's premisse.
     */
    private boolean isPremisse(Item _item) throws IOException, JDOMException {
        for (Rule rule : objectPersistence.getRules()) {
            for (Item premisse : rule.getPremisses()) {
                if (_item.getDescription().equals(premisse.getDescription()) &&
                        _item.getValue().equals(premisse.getValue())) {
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * Method that verify if an item is a rule's conclusion.
     */
    private boolean isConclusion(Item _item) throws IOException, JDOMException {
        for (Rule rule : objectPersistence.getRules()) {
            if (_item.getDescription().equals(rule.getConclusion().getDescription()) &&
                    _item.getValue().equals(rule.getConclusion().getValue())) {
                return true;
            }
        }
        return false;
    }
    
    private boolean isConclusionIgnoreValue(Item _item) throws IOException, JDOMException {
        for (Rule rule : objectPersistence.getRules()) {
            if (_item.getDescription().equals(rule.getConclusion().getDescription())) {
                return true;
            }
        }
        return false;
    }
    
}
